/*
 * CAudioTranscoder.cpp
 *
 *  Created on: 2017年3月6日
 *      Author: chuanjiang.zh
 */

#include "CAudioTranscoder.h"
#include "CLog.h"
#include <assert.h>

#include "TFileUtil.h"
#include <stdio.h>


namespace av
{

CAudioTranscoder::CAudioTranscoder():
		m_codecID(),
		m_channels(),
		m_sampleRate(),
		m_channelLayout(),
		m_decContext(),
		m_swrContext(),
		m_encoder()
{
	m_encoder.reset(AudioEncoderFactory::create(""), AudioEncoderFactory::destroy);

	m_encoder->setParam(AudioEncoder::kAdts, 1);

}

CAudioTranscoder::~CAudioTranscoder()
{
    close();
}

bool CAudioTranscoder::open(const MediaFormat& fmt, AVCodecID codecID, int channels, int sampleRate)
{
    bool done = false;

		CLog::info("CAudioTranscoder::open. fmt codec: %d, chl:%d, freq: %d => codec: %d, chl: %d, freq: %d\n",
		 fmt.m_audioCodec, fmt.m_channels, fmt.m_sampleRate,
		 codecID, channels, sampleRate);

    m_fmt = fmt;
	m_codecID = codecID;
	m_channels = channels;
	m_sampleRate = sampleRate;
	m_channelLayout = av_get_default_channel_layout(m_channels);

    int rc = openDecoder();
	if (rc != 0)
	{
		return false;
	}

	rc = openEncoder(codecID, channels, sampleRate);
    if (rc != 0)
    {
        close();
    }
    else
    {
        done = true;
    }
    return done;
}

void CAudioTranscoder::close()
{
	closeEncoder();
    closeDecoder();
}

bool CAudioTranscoder::isOpen()
{
	return isEncoderOpen();
}

bool CAudioTranscoder::getMediaFormat(MediaFormat& fmt)
{
	fmt.m_audioCodec = m_codecID;
	fmt.m_channels = m_channels;
	fmt.m_sampleRate = m_sampleRate;
	
	fmt.m_audioProfile = m_fmt.m_audioProfile;
	fmt.m_audioConfig = m_fmt.m_audioConfig;

	return true;
}

bool CAudioTranscoder::transcode(AVPacket* inPkt, AVPacket* outPkt)
{
	if (!m_decContext)
	{
		return false;
	}

	bool done = false;

	if (inPkt)
	{
		AVPacket pkt;
        av_init_packet(&pkt);

        pkt.data = inPkt->data;
        pkt.size = inPkt->size;

		while (pkt.size > 0)
		{
			AVFramePtr frame(av_frame_alloc(), ffmpeg::AVFrameDeleter());

			int gotFrame = 0;
			int bytes = avcodec_decode_audio4(m_decContext, frame.get(), &gotFrame, &pkt);
			if (bytes <= 0)
			{
				break;
			}

			frame->pts = inPkt->dts;
			if (frame->channel_layout <= 0)
			{
				frame->channel_layout = av_get_default_channel_layout(frame->channels);
			}

			pkt.size -= bytes;
			pkt.data += bytes;

			if (gotFrame)
			{
				//printf("transcode. pkt.size: %d, frame pts: %lld\n", inPkt->size, frame->pts);
				done = transcode(frame, outPkt);
			}
		}

	}

	return done;
}


int CAudioTranscoder::openEncoder(AVCodecID codecID, int channels, int sampleRate)
{
	int bitrate = sampleRate * 2;
	int ret = m_encoder->open(channels, sampleRate, codecID, bitrate);
	if (ret == 0)
	{
		MFormat& fmt = m_encoder->getFormat();

		m_fmt.m_audioCodec = fmt.audioCodec;
		m_fmt.m_channels = fmt.channels;
		m_fmt.m_sampleRate = fmt.sampleRate;
		m_fmt.m_audioBitrate = fmt.audioBitrate;
		m_fmt.m_audioProfile = fmt.audioProfile;

		if (fmt.config && fmt.configSize)
		{
			m_fmt.setAudioConfig(fmt.config, fmt.configSize);
		}
	}
	return ret;
}

void CAudioTranscoder::closeEncoder()
{
	if (m_swrContext)
	{
		swr_close(m_swrContext);
		swr_free(&m_swrContext);
		m_swrContext = NULL;
	}

	if (m_encoder)
	{
		m_encoder->close();
	}
}

bool CAudioTranscoder::isEncoderOpen()
{
	return (m_encoder && m_encoder->isOpen());
}

bool CAudioTranscoder::isSamleFormat(int fmt, int channels, int sampleRate)
{
	return (m_channels == channels) &&
        (m_sampleRate == sampleRate) &&
        (fmt == AV_SAMPLE_FMT_S16);
}

bool CAudioTranscoder::getAudioConfig(std::string& config)
{
    //todo
	return true;
}

bool CAudioTranscoder::transcode(AVFramePtr& frame, AVPacket* outPkt)
{
	
    if (frame->channel_layout == 0)
    {
        frame->channel_layout = av_get_default_channel_layout(frame->channels);
    }

	if (isSamleFormat(frame->format, frame->channels, frame->sample_rate))
	{
		return encode(frame, outPkt);
	}
	else
	{
		/// resample
        AVFramePtr outFrame(av_frame_alloc(), ffmpeg::AVFrameDeleter());

		outFrame->format = AV_SAMPLE_FMT_S16;
		outFrame->channels = m_channels;
		outFrame->channel_layout = m_channelLayout;
		outFrame->sample_rate = m_sampleRate;
		outFrame->pts = frame->pts;
        outFrame->nb_samples = frame->nb_samples;
        av_frame_get_buffer(outFrame.get(), 1);

		int rc = resample(frame.get(), outFrame.get());
		if (rc > 0)
		{
			return encode(outFrame, outPkt);
		}
		else
		{
			CLog::error("resample failed. \n");
			return false;
		}
	}
}

int CAudioTranscoder::resample(AVFrame* inFrame, AVFrame* outFrame)
{
	if (m_swrContext == NULL)
	{
		m_swrContext = swr_alloc();

		swr_config_frame(m_swrContext, outFrame, inFrame);

		int rc = swr_init(m_swrContext);
		if (rc != 0)
		{
			CLog::error("swr_init failed. rc:%d\n", rc);
		}
	}
	else
	{
		int delay = swr_get_delay(m_swrContext, outFrame->sample_rate);

		outFrame->nb_samples = inFrame->nb_samples + delay;
		av_frame_get_buffer(outFrame, 1);
	}

	swr_convert_frame(m_swrContext, outFrame, inFrame);

	return outFrame->nb_samples;
}

bool CAudioTranscoder::encode(AVFramePtr& frame, AVPacket* outPkt)
{
	bool got = false;

	uint8_t* outData = outPkt->data;
	int outCapacity = outPkt->size;
	outPkt->size = 0;

	int pktCount = 0;

	int sampleBytes = av_get_bytes_per_sample((AVSampleFormat)frame->format);
	uint8_t* data = frame->data[0];
	int size = frame->nb_samples * frame->channels * sampleBytes;

	//comn::FileUtil::write(data, size, "in.pcm", true);

	while (size > 0)
	{
		MPacket mpkt = MPacket();
		int count = m_encoder->encode(data, size, frame->pts, &mpkt);
		data += count;
		size -= count;

		if (mpkt.size <= 0)
		{
			continue;
		}

		//printf("out pkt. frame->pts: %lld, size: %d, pts: %lld\n", frame->pts, mpkt.size, mpkt.pts);

		got = true;

		if (outPkt->data && (outCapacity >= mpkt.size))
		{
			memcpy(outData, mpkt.data, mpkt.size);
			outData += mpkt.size;
			outCapacity -= mpkt.size;

			outPkt->size += mpkt.size;

			outPkt->duration = mpkt.duration;
			outPkt->dts = mpkt.pts;
			outPkt->pts = mpkt.pts;
			outPkt->flags = mpkt.flags;

			pktCount++;
			
			if (pktCount > 1)
			{
				CLog::error("should not occur. pktCount: %d\n", pktCount);
			}
		}
	}
	return got;
}

int CAudioTranscoder::openDecoder()
{
	AVCodec* pCodec = avcodec_find_decoder((AVCodecID)m_fmt.m_audioCodec);
	if (!pCodec)
	{
		return EBADF;
	}

	m_decContext = avcodec_alloc_context3(pCodec);

	m_decContext->refcounted_frames = 1;

	m_decContext->codec_type = AVMEDIA_TYPE_AUDIO;
	m_decContext->channels = m_fmt.m_channels;
	m_decContext->sample_rate = m_fmt.m_sampleRate;

	if (m_fmt.m_audioConfig.size())
	{
		m_decContext->extradata = (uint8_t*)av_memdup(m_fmt.m_audioConfig.c_str(), m_fmt.m_audioConfig.size());
		m_decContext->extradata_size = m_fmt.m_audioConfig.size();
	}

	if (avcodec_open2(m_decContext, pCodec, NULL) < 0)
	{
		avcodec_close(m_decContext);
		av_free(m_decContext);
		m_decContext = NULL;
		return EBADF;
	}

	return 0;
}

void CAudioTranscoder::closeDecoder()
{
	if (m_decContext)
	{
		avcodec_close(m_decContext);
		av_free(m_decContext);
		m_decContext = NULL;
	}
}




} /* namespace av */
